package com.example.seckilldemo.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.seckilldemo.config.AccessLimit;
import com.example.seckilldemo.exception.GlobalException;
import com.example.seckilldemo.pojo.Order;
import com.example.seckilldemo.pojo.SeckillOrder;
import com.example.seckilldemo.pojo.User;
import com.example.seckilldemo.rabbitmq.MQSeander;
import com.example.seckilldemo.service.IGoodsService;
import com.example.seckilldemo.service.IOrderService;
import com.example.seckilldemo.service.ISeckillOrderService;
import com.example.seckilldemo.service.impl.SeckillOrderServiceImpl;
import com.example.seckilldemo.utils.JsonUtil;
import com.example.seckilldemo.vo.GoodsVo;
import com.example.seckilldemo.vo.RespBean;
import com.example.seckilldemo.vo.RespBeanEnum;
import com.example.seckilldemo.vo.SeckillMessageVo;
import com.wf.captcha.ArithmeticCaptcha;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * TODO
 *
 * @author 59244
 * @date 2021/11/20 18:13
 */
@Slf4j
@Controller
@RequestMapping("/seckill")
public class SeckillController implements InitializingBean {

    @Autowired
    IGoodsService goodsService;
    @Autowired
    ISeckillOrderService seckillOrderService;
    @Autowired
    IOrderService orderService;
    @Autowired
    RedisTemplate redisTemplate;
    @Autowired
    MQSeander mqSeander;
    @Autowired
    DefaultRedisScript redisScript;

    private Map<Long, Boolean> EmptyStockMap = new HashMap<>();

    /**
     * 功能描述：秒杀
     * Windows优化前QPS：1011
     * Windows页面静态化+缓存优化QPS：2319
     * Windows接口优化：2729
     *
     * Linux优化前QPS：424
     *
     * @params:
     * @return：
     * @author: 59244
     * @time: 2021/11/20 18:14
     */
    @RequestMapping("/doseckill2")
    public String doseckill2(Model model, User user, Long goodsId){
        if(user == null){
            return "login";
        }
        model.addAttribute("user", user);
        //判断库存是否充足
        GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodsId);
        if(goodsVo.getStockCount() <= 0){
            model.addAttribute("errMsg", RespBeanEnum.EMPTY_ERROR.getMessage());
            return "seckillFail";
        }
        //判断用户是否已经秒杀过这个商品
        SeckillOrder seckillOrder = seckillOrderService.getOne(new QueryWrapper<SeckillOrder>()
                .eq("user_id",user.getId())
                .eq("goods_id",goodsId));
        if(seckillOrder != null){
            model.addAttribute("errMsg",RespBeanEnum.REPEAT_ERROR.getMessage());
            return "seckillFail";
        }
        Order order = orderService.seckillToOrder(user, goodsVo);
        model.addAttribute("order",order);
        model.addAttribute("goods",goodsVo);
        return "orderDetail";
    }

    /**
     * 功能描述：秒杀   页面静态化优化
     *
     * @params:
     * @return：
     * @author: 59244
     * @time: 2021/11/22 10:47
     */
    @PostMapping("/doseckill1")
    @ResponseBody
    public RespBean doseckill1(Model model, User user, Long goodsId){
        if(user == null){
            return RespBean.error(RespBeanEnum.SEAAION_ERROR);
        }
//        model.addAttribute("user", user);
        //判断库存是否充足
        GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodsId);
        if(goodsVo.getStockCount() <= 0){
//            model.addAttribute("errMsg", RespBeanEnum.EMPTY_ERROR.getMessage());
            return RespBean.error(RespBeanEnum.EMPTY_ERROR);
        }
        //判断用户是否已经秒杀过这个商品
//        SeckillOrder seckillOrder = seckillOrderService.getOne(new QueryWrapper<SeckillOrder>()
//                .eq("user_id",user.getId())
//                .eq("goods_id",goodsId));
        //用redis来优化查询数据库
        SeckillOrder seckillOrder =
                (SeckillOrder) redisTemplate.opsForValue().get("seckillOrder:" + user.getId() + "+" + goodsId);
        if(seckillOrder != null){
//            model.addAttribute("errMsg",RespBeanEnum.REPEAT_ERROR.getMessage());
            return RespBean.error(RespBeanEnum.REPEAT_ERROR);
        }
        //生成订单和秒杀订单，返回订单即可
        Order order = orderService.seckillToOrder(user, goodsVo);
//        model.addAttribute("order",order);
//        model.addAttribute("goods",goodsVo);
        return RespBean.success(order);
    }

    /**
     * 功能描述：秒杀  redis预减库存，内存标记，RabbitMQ异步发送订单生成的消息
     *
     * @params:
     * @return：
     * @author: 59244
     * @time: 2021/11/22 10:47
     */
    @PostMapping("/{path}/doseckill")
    @ResponseBody
    public RespBean doseckill(@PathVariable String path, User user, Long goodsId){
        if(user == null){
            return RespBean.error(RespBeanEnum.SEAAION_ERROR);
        }
        ValueOperations valueOperations = redisTemplate.opsForValue();
        //验证秒杀地址 是否为该用户的地址
        Boolean check = orderService.checkPath(path,user,goodsId);
        if(!check){
            return RespBean.error(RespBeanEnum.REQUEST_ILLEGAL);
        }
        //判断用户是否已经秒杀过这个商品
        SeckillOrder seckillOrder =
                (SeckillOrder) valueOperations.get("seckillOrder:" + user.getId() + "+" + goodsId);
        if(seckillOrder != null){
            return RespBean.error(RespBeanEnum.REPEAT_ERROR);
        }
        //通过内存标记，减少redis的访问
        if(EmptyStockMap.get(goodsId)){
            return RespBean.error(RespBeanEnum.EMPTY_ERROR);
        }
        //redis预减库存
//        Long stock = valueOperations.decrement("seckillGood:" + goodsId);//这个操作是原子性的
        //用lua脚本去执行自减操作，也是原子性，lua脚本可以放在java服务端、也可以放在redis服务端
        Long stock = (Long) redisTemplate.execute(redisScript,
                Collections.singletonList("seckillGood:" + goodsId), Collections.EMPTY_LIST);
        if(stock < 0){
            valueOperations.increment("seckillGood:" + goodsId);
            EmptyStockMap.put(goodsId,true);
            return RespBean.error(RespBeanEnum.EMPTY_ERROR);
        }
        //生成秒杀消息对象
        SeckillMessageVo seckillMessageVo = new SeckillMessageVo(user, goodsId);
        //RabbitMQ发送消息
        mqSeander.sendSeckillMessage(JsonUtil.object2JsonStr(seckillMessageVo));

        return RespBean.success(0);
    }

    /**
     * 功能描述：查询用户的秒杀订单
     *
     * @params:
     * @return： orderId
     * @author: 59244
     * @time: 2021/11/27 16:57
     */
    @GetMapping("/result")
    @ResponseBody
    public RespBean result(User user, Long goodsId){
        if(user == null){
            return RespBean.error(RespBeanEnum.SEAAION_ERROR);
        }
        Long orderId = seckillOrderService.getresult(user,goodsId);
        return RespBean.success(orderId);
    }

    /**
     * 功能描述：获取到真正的秒杀接口路径
     *
     * @params:
     * @return：
     * @author: 59244
     * @time: 2021/11/28 14:08
     */
    @AccessLimit(second=5,maxCount=5,needLogin=true)
    @GetMapping("/path")
    @ResponseBody
    public RespBean getPath(User user, Long goodsId, String captcha){
        if(user == null){
            return RespBean.error(RespBeanEnum.SEAAION_ERROR);
        }

        //检查验证码是否正确
        Boolean check = orderService.checkCaptcha(captcha,user,goodsId);
        if(!check){
            return RespBean.error(RespBeanEnum.CAPTCHA_ERROR);
        }
        String path = orderService.createPath(user,goodsId);
        return RespBean.success(path);
    }

    /**
     * 功能描述：验证码
     *
     * @params:
     * @return：
     * @author: 59244
     * @time: 2021/11/28 15:08
     */
    @GetMapping("/captcha")
    @ResponseBody
    public void captcha(User user, Long goodsId, HttpServletResponse response){
        if(user == null || goodsId < 0){
            throw new GlobalException(RespBeanEnum.REQUEST_ILLEGAL);
        }
        //设置请求头为图片类型
        response.setContentType("image/jpg");
        response.setHeader("Pragma", "No-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);
        //生成验证码，将结果放入redis中
        ArithmeticCaptcha arithmeticCaptcha = new ArithmeticCaptcha(130,32, 3);
        redisTemplate.opsForValue().set("captcha:"+user.getId()+":"+goodsId, arithmeticCaptcha.text(),
                300, TimeUnit.SECONDS);
        try {
            arithmeticCaptcha.out(response.getOutputStream());
        } catch (IOException e) {
            log.error("验证码生成失败",e.getMessage());
        }
    }


    /**
     * 功能描述：项目启动后，把商品库存加载到redis中
     *
     * @params:
     * @return：
     * @author: 59244
     * @time: 2021/11/27 15:06
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        List<GoodsVo> goodsVoList = goodsService.findGoodsVo();
        if(CollectionUtils.isEmpty(goodsVoList)){
            return ;
        }
        goodsVoList.forEach(goodsVo ->
        {
            redisTemplate.opsForValue().set("seckillGood:" + goodsVo.getId(), goodsVo.getStockCount());
            EmptyStockMap.put(goodsVo.getId(), false);
        });
    }
}
